package com.pronhub.cloud.shop.mq;

import com.pronhub.cloud.shop.msg.SeckillCodeMsg;
import com.pronhub.cloud.shop.service.IOrderInfoService;
import com.pronhub.cloud.shop.service.ISeckillGoodService;
import com.pronhub.cloud.shop.service.ISeckillOrderService;
import com.pronhub.cloud.shop.web.controller.OrderInfoController;
import com.pronhub.shop.cloud.BussinessException;
import com.rabbitmq.client.Channel;
import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.TopicExchange;
import org.springframework.amqp.rabbit.annotation.Exchange;
import org.springframework.amqp.rabbit.annotation.Queue;
import org.springframework.amqp.rabbit.annotation.QueueBinding;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.amqp.support.AmqpHeaders;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.messaging.handler.annotation.Header;
import org.springframework.messaging.handler.annotation.Payload;
import org.springframework.stereotype.Component;

import java.io.IOException;
import java.net.BindException;
import java.util.HashMap;
import java.util.Map;

/**
 * Created by wolfcode-lanxw
 */
@Component
public class OrderMQListener {
    //订单服务的
    @Autowired
    private IOrderInfoService orderInfoService;

    //MQ发送消息的
    @Autowired
    private RabbitTemplate rabbitTemplate;

    //商品信息的
    @Autowired
    private ISeckillGoodService seckillGoodService;


    //异步下单 就是第七步发送消息的异步下单
    @RabbitListener(queuesToDeclare = @Queue(name = MQConstants.ORDER_PEDDING_QUEUE)) //队列名称
    public void handleOrderPeddingQueue(@Payload OrderMessage orderMessage, @Header(AmqpHeaders.DELIVERY_TAG) Long deliveryTag, Channel channel) throws IOException {
        //@Payload 反序列化的一个标签在这里  因为传输的数据是序列化的 我们需要反序列化的
        HashMap<Object, Object> param = new HashMap<>(); //创建一个集合 就是返回结果而已
        param.put("uuid",orderMessage.getUuid());//网页的发过来的UUID
        param.put("seckillId",orderMessage.getSeckillId());//订单服务类型
        try {
            //业务逻辑的实现 订单的成功下单了 返回消息请支付
            String orderNo = orderInfoService.insertOrder(orderMessage.getUserId(), orderMessage.getSeckillId());
            //成功先打了  返回的是成功下单的消息
            param.put("orderNo",orderNo);//订单编号
            param.put("msg","MQConstants.ORDER_PEDDING_QUEUE");//给前台显示的消息
            rabbitTemplate.convertAndSend(MQConstants.ORDER_RESULT_EXCHANGE,MQConstants.ORDER_RESULT_SUCCESS_KEY,param);
            System.err.println("成功下单");
        }catch (Exception e){
            //如果没有下单成功 库存的回滚
            param.put("code", SeckillCodeMsg.SECKILL_ERROR.getCode());//抢购失败返回的错误代码
            param.put("msg",SeckillCodeMsg.SECKILL_ERROR.getMsg());//返回抢购失败的消息
            //给MQ发送另一条消息 失败的消息  就行下面的订单处理失败的操作
            rabbitTemplate.convertAndSend(MQConstants.ORDER_RESULT_EXCHANGE,MQConstants.ORDER_RESULT_FAIL_KEY,param);
        }finally {
            //手动签收消息 就是告诉前面我们要手动签收这个消息
            channel.basicAck(deliveryTag,false);
        }
    }

    /**处理下单失败
     *  库存的回补作业
     *
     *  Redis+1 同时取消本地的表示 放请求进来
     */
    //处理下单失败
    @RabbitListener(bindings = @QueueBinding(
            value = @Queue(name = MQConstants.ORDER_RESULT_FAIL_QUEUE),
            exchange = @Exchange(name = MQConstants.ORDER_RESULT_EXCHANGE,type = "topic"),
            key = MQConstants.ORDER_RESULT_FAIL_KEY
    ))
    public void handleOrderFailQueue(@Payload Map<String,Object> param,@Header(AmqpHeaders.DELIVERY_TAG) Long deliveryTag, Channel channel) throws IOException {
        //进行消息的的回滚处理
        try {
            //得到秒杀场次号码
            Long seckillId = (Long) param.get("seckillId");
            //对相对应的秒杀场次的Redis+1操作
            seckillGoodService.syncRedisStockCount(seckillId);
        }catch (Exception e){
            e.printStackTrace();
            //同步预库存失败，发送短信/邮件，人工处理.
        }finally {
            //手动签收消息 就是告诉前面我们要手动签收这个消息
            channel.basicAck(deliveryTag,false);
        }
    }
    //声明但不监听，下单成功的队列
    @Bean
    public org.springframework.amqp.core.Queue orderSuccessDelayQueue(){
        Map<String,Object> arguments = new HashMap<>();
        arguments.put("x-dead-letter-exchange",MQConstants.DELAY_EXCHANGE);//死信队列的交换机
        arguments.put("x-dead-letter-routing-key",MQConstants.ORDER_DELAY_KEY);//死信队列的路由key
        arguments.put("x-message-ttl",1000*60*15);//消息的有效时间.
        return new org.springframework.amqp.core.Queue(MQConstants.ORDER_RESULT_SUCCESS_DELAY_QUEUE,true,false,false,arguments);
    }
    //定义orderSuccessDelayQueue和哪个交换机进行绑定.
    @Bean
    public Binding binding1a(org.springframework.amqp.core.Queue orderSuccessDelayQueue) {//形参名字必须要和上面的方法名一致.
        return BindingBuilder.bind(orderSuccessDelayQueue)
                .to(new TopicExchange(MQConstants.ORDER_RESULT_EXCHANGE))
                .with(MQConstants.ORDER_RESULT_SUCCESS_KEY);
    }
    //超时未支付的订单
    @RabbitListener(bindings = @QueueBinding(
            value = @Queue(name = MQConstants.ORDER_TIMEOUT_QUEUE),
            exchange = @Exchange(name = MQConstants.DELAY_EXCHANGE,type = "topic"),
            key = MQConstants.ORDER_DELAY_KEY
    ))
    public void handleTimeQueue(@Payload Map<String,Object> param,@Header(AmqpHeaders.DELIVERY_TAG) Long deliveryTag, Channel channel) throws IOException {
        //去数据库查询该订单的状态,如果订单是未支付的订单 进行处理 数据的回滚
        try {
            Long seckillId = (Long) param.get("seckillId");
            String orderNo = (String) param.get("orderNo");
            //判断订单的状态.
            orderInfoService.cancelOrder(seckillId,orderNo);
        }catch(BussinessException ex){
            ex.printStackTrace();
            //超时取消订单的时候没有更新到订单状态(可能刚好支付和超时取消碰在一块),通过mq发送消息到某个队列，通知客服人员检查订单状态(人工处理)
        }catch(Exception e){
            e.printStackTrace();
            //超时取消订单的时候没有更新到订单状态(可能刚好支付和超时取消碰在一块),通过mq发送消息到某个队列，通知客服人员检查订单状态(人工处理)
        }finally {
            channel.basicAck(deliveryTag,false);
        }
    }
    //取消本地标识  这个在服务层那边进行调用
    @RabbitListener(bindings = @QueueBinding(
            value = @Queue,
            exchange = @Exchange(name = MQConstants.SECKILL_OVER_SIGN_PUBSUB_EX,type = "fanout")
    ))
    public void handleCancleLocalSignPubSub(Long seckillId,@Header(AmqpHeaders.DELIVERY_TAG) Long deliveryTag, Channel channel) throws IOException {
        try{
            System.out.println("取消本地标识"); //就是将单台服务器的那个变量设为false
            OrderInfoController.isStockOverMap.put(seckillId,false);
        }catch(Exception e){
            e.printStackTrace();
        }finally {
            channel.basicAck(deliveryTag,false);
        }
    }
}
